<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MPush WebSocket Client</title>
</head>
<body>

<script type="text/javascript">
    "use strict";
    (function (window) {
        let socket, session = {}, ID_SEQ = 1;
        let config = {listener: null, log: console};

        let listener = {
            onOpened: function (event) {
                if (config.listener != null) {
                    config.listener.onOpened(event);
                }
                handshake();
            },
            onClosed: function (event) {
                if (config.listener != null) {
                    config.listener.onClosed(event);
                }
                session = {};
                ID_SEQ = 1;
                socket = null;
            },
            onHandshake: function () {
                session.handshakeOk = true;
                if (config.listener != null) {
                    config.listener.onHandshake();
                }
                if (config.userId) {
                    bindUser(config.userId, config.tags);
                }
            },
            onBindUser: function (success) {
                if (config.listener != null) {
                    config.listener.onBindUser(success);
                }
            },
            onReceivePush: function (message, messageId) {
                if (config.listener != null) {
                    config.listener.onReceivePush(message, messageId);
                }
            },
            onKickUser: function (userId, deviceId) {
                if (config.listener != null) {
                    config.listener.onKickUser(userId, deviceId);
                }
                doClose(-1, "kick user");
            }
        };

        const Command = {
            HANDSHAKE: 2,
            BIND: 5,
            UNBIND: 6,
            ERROR: 10,
            OK: 11,
            KICK: 13,
            PUSH: 15,
            ACK: 23,
            UNKNOWN: -1
        };

        function Packet(cmd, body, sessionId) {
            return {
                cmd: cmd,
                flags: 16,
                sessionId: sessionId || ID_SEQ++,
                body: body
            }
        }

        function handshake() {
            config.log.info("<<< send handshake message, deviceId=" + config.deviceId);
            send(Packet(Command.HANDSHAKE, {
                    deviceId: config.deviceId,
                    osName: config.osName,
                    osVersion: config.osVersion,
                    clientVersion: config.clientVersion
                })
            );
        }

        function bindUser(userId, tags) {
            if (userId && userId != session.userId) {
                config.log.info("<<< send bindUser message, userId=" + userId);
                session.userId = userId;
                session.tags = tags;
                send(Packet(Command.BIND, {userId: userId, tags: tags}));
            } else {
                config.log.error("user " + userId + " already bind");
            }
        }

        function ack(sessionId) {
            config.log.debug("<<< send ack message, sessionId=" + sessionId);
            send(Packet(Command.ACK, null, sessionId));
        }

        function send(packet) {
            if (!socket) {
                return;
            }
            let message = JSON.stringify(packet);
            if (socket.readyState == WebSocket.OPEN) {
                socket.send(message);
                config.log.debug("<<< send message to server, message=" + message);
            } else {
                config.log.error("The socket is not open. message=" + message);
            }
        }

        function dispatch(packet) {
            switch (packet.cmd) {
                case Command.HANDSHAKE: {
                    config.log.info(">>> handshake ok.");
                    listener.onHandshake();
                    break;
                }
                case Command.OK: {
                    if (packet.body.cmd == Command.BIND) {
                        config.log.info(">>> bind user ok.");
                        listener.onBindUser(true);
                    }
                    break;
                }
                case Command.ERROR: {
                    if (packet.body.cmd == Command.BIND) {
                        config.log.warn(">>> bind user failure.");
                        listener.onBindUser(false);
                    }
                    break;
                }

                case Command.KICK: {
                    if (session.userId == packet.body.userId && config.deviceId == packet.body.deviceId) {
                        config.log.warn(">>> receive kick user.");
                        listener.onKickUser(packet.body.userId, packet.body.deviceId);
                    }
                    break;
                }

                case Command.PUSH: {
                    config.log.info(">>> receive push, content=" + packet.body.content);
                    let sessionId;
                    if ((packet.flags & 8) != 0) {
                        ack(packet.sessionId);
                    } else {
                        sessionId = packet.sessionId
                    }
                    listener.onReceivePush(packet.body.content, sessionId);
                    break;
                }
            }
        }

        function onReceive(event) {
            config.log.debug(">>> receive packet=" + event.data);
            dispatch(JSON.parse(event.data))
        }

        function onOpen(event) {
            config.log.info("Web Socket opened!");
            listener.onOpened(event);
        }

        function onClose(event) {
            config.log.info("Web Socket closed!");
            listener.onClosed(event);
        }

        function onError(event) {
            config.log.info("Web Socket receive, error");
            doClose();
        }

        function doClose(code, reason) {
            if (socket) socket.close();
            config.log.info("try close web socket client, reason=" + reason);
        }

        function doConnect(cfg) {
            config = copy(cfg);
            socket = new WebSocket(config.url);
            socket.onmessage = onReceive;
            socket.onopen = onOpen;
            socket.onclose = onClose;
            socket.onerror = onError;
            config.log.debug("try connect server, url=" + config.url);
        }

        function copy(cfg) {
            for (let p in cfg) {
                if (cfg.hasOwnProperty(p)) {
                    config[p] = cfg[p];
                }
            }
            return config;
        }

        window.mpush = {
            connect: doConnect,
            close: doClose,
            bindUser: bindUser
        }
    })(window);

    function $(id) {
        return document.getElementById(id);
    }

    let log = {
        log: function () {
            $("responseText").value += (new Date().toLocaleString() + " " + (Array.prototype.join.call(arguments, "") + "\r\n"));
        }
    };
    log.debug = log.info = log.warn = log.error = log.log;

    function connect() {
        mpush.connect({
            url: $("url").value,
            userId: $("userId").value,
            deviceId: "test-1001",
            osName: "web",
            osVersion: navigator.userAgent,
            clientVersion: "1.0",
            log: log
        });
    }

    function bind() {
        mpush.bindUser($("userId").value)
    }

</script>
<form onsubmit="return false;">
    <label> Server Url:
        <input type="text" id="url" value="ws://127.0.0.1:8008/">
    </label>
    <input type="button" value="Connect" onclick="connect()">
    <br>
    <label> Bind User:
        <input type="text" id="userId" value="user-0">
    </label>
    <input type="button" value="bind" onclick="bind()">
    <h3><label for="responseText">Output</label></h3>
    <textarea id="responseText" style="width:100%;height:500px;"></textarea>
</form>

</body>
</html>